﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using OpenSage.FileFormats.RefPack;

namespace OpenSage.FileFormats.Big;

public class BigArchiveEntry
{
    internal uint Offset;

    public BigArchive Archive { get; }

    public string FullName { get; }
    public string Name => FullName.Split('\\').Last();
    public uint Length { get; internal set; }

    // Internal members
    internal bool OnDisk { get; set; }
    internal MemoryStream? OutstandingWriteStream { get; set; }
    internal uint OutstandingOffset { get; set; }
    internal bool CurrentlyOpenForWrite { get; set; }

    internal BigArchiveEntry(BigArchive archive, string name, uint offset, uint size)
    {
        Archive = archive;
        FullName = name;
        Offset = offset;
        Length = size;
        OnDisk = true;
        OutstandingOffset = 0;
        CurrentlyOpenForWrite = false;
    }

    internal BigArchiveEntry(BigArchive archive, string name)
    {
        Archive = archive;
        FullName = name;
        Length = 0;
        OnDisk = false;
        CurrentlyOpenForWrite = false;
    }

    public void Delete()
    {
        if (Archive == null)
            return;

        if (CurrentlyOpenForWrite)
            throw new IOException("Entry is currently being written to");

        if (Archive.Mode != BigArchiveMode.Update)
            throw new NotSupportedException("Delete only works in update mode");

        Archive.DeleteEntry(this);
    }

    private Stream OpenInReadMode()
    {
        var bigStream = new BigArchiveEntryStream(this, Offset);

        // Wrapping BigStream in a BufferedStream significantly improves performance.
        var bufferedBigStream = new BufferedStream(bigStream);

        // Check for refpack compression header.
        // C&C3 started using refpack compression for .big archive entries.
        if (RefPackStream.IsProbablyRefPackCompressed(bufferedBigStream))
        {
            var refPackStream = new RefPackStream(bufferedBigStream);

            // Wrap RefPackStream in (another) BufferedStream, to improve performance.
            return new BufferedStream(refPackStream);
        }

        return bufferedBigStream;
    }

    private Stream OpenInWriteMode()
    {
        if (CurrentlyOpenForWrite)
            throw new IOException("Can only write once and one entry at a time!");

        CurrentlyOpenForWrite = true;

        var bigStream = new BigArchiveEntryStream(this, Offset);

        // Check for refpack compression header.
        // C&C3 started using refpack compression for .big archive entries.
        if (RefPackStream.IsProbablyRefPackCompressed(bigStream))
        {
            return new RefPackStream(bigStream);
        }

        return bigStream;
    }

    private Stream OpenInUpdateMode()
    {
        if (CurrentlyOpenForWrite)
            throw new IOException("Can only open a single write stream!");

        CurrentlyOpenForWrite = true;

        var bigStream = new BigArchiveEntryStream(this, Offset);

        // Check for refpack compression header.
        // C&C3 started using refpack compression for .big archive entries.
        if (RefPackStream.IsProbablyRefPackCompressed(bigStream))
        {
            return new RefPackStream(bigStream);
        }

        return bigStream;
    }

    public Stream Open()
    {
        switch (Archive.Mode)
        {
            case BigArchiveMode.Read:
                return OpenInReadMode();
            case BigArchiveMode.Create:
                return OpenInWriteMode();
            case BigArchiveMode.Update:
            default:
                Debug.Assert(Archive.Mode == BigArchiveMode.Update);
                return OpenInUpdateMode();
        }
    }

    internal void CloseStreams()
    {
        // if the user left the stream open, close the underlying stream for them
        OutstandingWriteStream?.Dispose();
        OutstandingWriteStream = null;
    }
}
